package io.github.edwinliu.anynet;

import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.RouteInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;

import java.lang.reflect.Constructor;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Arrays;
import java.util.Enumeration;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;

public class AnyNet implements IXposedHookZygoteInit,IXposedHookLoadPackage {
    static String TAG = "AnyNet";

    //private static  XSharedPreferences mXSharedPref = null;

    private static  LinkProperties mLinkProperties =null;
    private static NetworkInfo mNetworkInfo =null;
    private static long mUpTimeSec =0;
    private static boolean ｍbSystemIsReady =false;

    private static String mIpAddr="192.168.1.2";
    private static String mGateway="192.168.1.1";
    private static String mIfName="rndis0";

    //public final static int NETWORK_TYPE_UNKNOW=-1;
    public final static int NETWORK_TYPE_NONE=0;
    public final static int NETWORK_TYPE_MOBILE=1;
    public final static int NETWORK_TYPE_WIFI=2;

    public static ServerThread serverThread;

/*    boolean bFakeVpn=SocketController.getVpn();
    int nettype =SocketController.getType();*/
   // boolean bFakeVpn=false;
   // int nettype =NETWORK_TYPE_NONE;

    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {
        XposedBridge.log(TAG + " initZygote "+startupParam.modulePath+" "+startupParam.startsSystemServer);
    /*    if(startupParam.startsSystemServer) {
            serverThread = new ServerThread();
            serverThread.start();
        }*/
    }

    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log(TAG+" handleLoadPackage:"+ lpparam.packageName);

        final Context[] context = new Context[1];
        XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                context[0] = (Context) param.args[0];
                //bFakeVpn=ProviderUtils.getInstance(context[0]).getBooleanValue(ProviderUtils.KEY_NAME_VPN);
                //nettype=ProviderUtils.getInstance(context[0]).getIntValue(ProviderUtils.KEY_NAME_NETTYPE);
                XposedBridge.log(TAG+" handleLoadPackage attach "+ context[0]);
            }
        });


        //系统设置里，开启VPN时会调用这个
        try {
            final   Class<?>  ConnectivityServiceClazz = XposedHelpers.findClass("com.android.server.ConnectivityService", lpparam.classLoader);
            if(ConnectivityServiceClazz!=null) {
                XposedBridge.log(TAG + " findClass ConnectivityService " + lpparam.packageName + " " + lpparam.processName);

                XposedHelpers.findAndHookMethod(ConnectivityServiceClazz, "getActiveLinkProperties",
                        new XC_MethodHook() {
                            @Override
                            protected void afterHookedMethod(MethodHookParam param)  {
                                if(context[0]==null)
                                {
                                    XposedBridge.log(TAG + " getActiveLinkProperties context[0]==null");
                                    return;
                                }
                                boolean bFakeVpn=ProviderUtils.getInstance(context[0])
                                        .getBooleanValue(ProviderUtils.KEY_NAME_VPN);
                                XposedBridge.log(TAG + " afterHookedMethod getActiveLinkProperties "+bFakeVpn);
                                if(bFakeVpn)
                                {
                                    mLinkProperties=creatLinkProp(lpparam);
                                    param.setResult(mLinkProperties);
                                }
                            }
                        });
            }
        } catch (Throwable th) {
           // XposedBridge.log(TAG+"ConnectivityService findClass Throwable ");
        }



        Class<?> ConnectivityManagerClazz = null;
        try {
            ConnectivityManagerClazz = XposedHelpers.findClass("android.net.ConnectivityManager", lpparam.classLoader);
        } catch (Throwable th) {
            XposedBridge.log(TAG + " handleLoadPackage " + th.toString());
        }
        try {
            XposedHelpers.findAndHookMethod(ConnectivityManagerClazz, "getActiveNetworkInfo",
                    new XC_MethodHook() {
                public void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if(context[0]==null)
                    {
                        XposedBridge.log(TAG + " getActiveNetworkInfo context[0]==null");
                        return;
                    }
                    int nettype=ProviderUtils.getInstance(context[0]).getIntValue(ProviderUtils.KEY_NAME_NETTYPE);
                    if (nettype != NETWORK_TYPE_NONE) {
                        NetworkInfo networkInfo = null;
                        if (nettype == NETWORK_TYPE_MOBILE) {
                            networkInfo = newNetWorkInfo(ConnectivityManager.TYPE_MOBILE);
                            Log.i(TAG, "getActiveNetworkInfo : mobile networkInfo created");
                        } else {
                            networkInfo = newNetWorkInfo(ConnectivityManager.TYPE_WIFI);
                            Log.i(TAG, "getActiveNetworkInfo : WIFI networkInfo created");
                        }

                        fakeNetworkInfo(networkInfo);
                        param.setResult(networkInfo);
                    }

                }
            });
        } catch (Throwable t) {
            Log.e(TAG, "getActiveNetworkInfo "+t.getMessage());
        }


        try {
            XposedHelpers.findAndHookMethod(ConnectivityManagerClazz, "getActiveNetworkInfoForUid", Integer.TYPE, new XC_MethodHook() {
                public void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if(context[0]==null)
                    {
                        XposedBridge.log(TAG + " getActiveNetworkInfoForUid context[0]==null");
                        return;
                    }
                    int nettype=ProviderUtils.getInstance(context[0]).getIntValue(ProviderUtils.KEY_NAME_NETTYPE);
                    if (nettype != NETWORK_TYPE_NONE) {
                        NetworkInfo networkInfo = null;

                        if (nettype == NETWORK_TYPE_MOBILE) {
                                networkInfo = newNetWorkInfo(ConnectivityManager.TYPE_MOBILE);
                                Log.i(TAG, "getActiveNetworkInfoForUid : mobile networkInfo created");
                        } else {
                                networkInfo = newNetWorkInfo(ConnectivityManager.TYPE_WIFI);
                                Log.i(TAG, "getActiveNetworkInfoForUid : WIFI networkInfo created");
                        }

                        fakeNetworkInfo(networkInfo);
                        param.setResult(networkInfo);
                    }
                }
            });
        } catch (Throwable t) {
            Log.e(TAG, "getActiveNetworkInfoForUid "+t.getMessage());
        }



        try {
            XposedHelpers.findAndHookMethod(ConnectivityManagerClazz, "getNetworkInfo", Integer.TYPE, new XC_MethodHook() {
                public void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if(context[0]==null)
                    {
                        XposedBridge.log(TAG + " getNetworkInfo context[0]==null");
                        return;
                    }

                    int nettype=ProviderUtils.getInstance(context[0]).getIntValue(ProviderUtils.KEY_NAME_NETTYPE);
                    if (nettype != NETWORK_TYPE_NONE) {
                        NetworkInfo networkInfo = null;
                        if (nettype == NETWORK_TYPE_MOBILE) {
                            networkInfo = newNetWorkInfo(ConnectivityManager.TYPE_MOBILE);
                            Log.i(TAG, "getNetworkInfo : mobile networkInfo created");
                        } else {
                            networkInfo = newNetWorkInfo(ConnectivityManager.TYPE_WIFI);
                            Log.i(TAG, "getNetworkInfo : WIFI networkInfo created");
                        }

                        fakeNetworkInfo(networkInfo);
                        param.setResult(networkInfo);
                    }
                }
            });
        } catch (Throwable t) {
            Log.e(TAG,"getNetworkInfo " +t.getMessage());
        }


        try {
            XposedHelpers.findAndHookMethod(ConnectivityManagerClazz, "getAllNetworkInfo", new XC_MethodHook() {
                public void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if(context[0]==null)
                    {
                        XposedBridge.log(TAG + " getAllNetworkInfo context[0]==null");
                        return;
                    }

                    int nettype=ProviderUtils.getInstance(context[0]).getIntValue(ProviderUtils.KEY_NAME_NETTYPE);

                    if (nettype != NETWORK_TYPE_NONE) {
                        Object res = param.getResult();
                        if (res != null) {
                            NetworkInfo[] networks = (NetworkInfo[]) res;
                            boolean wifiFound = false;
                            boolean mobileFound = false;
                            for (NetworkInfo info : networks) {
                                fakeNetworkInfo(info);
                                if (info.getType() == 1) {
                                    wifiFound = true;
                                }
                                if (info.getType() == 0) {
                                    mobileFound = true;
                                }
                            }
                            NetworkInfo[] networks2;
                            if (nettype == NETWORK_TYPE_WIFI && !wifiFound) {
                                Log.i(TAG, " new wifi networkInfo");
                                NetworkInfo Info2 = newNetWorkInfo(ConnectivityManager.TYPE_WIFI);
                                fakeNetworkInfo(Info2);
                                networks2 = (NetworkInfo[]) Arrays.copyOf(networks, networks.length + 1);
                                networks2[networks.length] = Info2;
                                param.setResult(networks2);
                            } else if (nettype == NETWORK_TYPE_MOBILE && !mobileFound) {
                                Log.i(TAG, " new moblie networkInfo");
                                NetworkInfo Info2 = newNetWorkInfo(ConnectivityManager.TYPE_MOBILE);
                                fakeNetworkInfo(Info2);
                                networks2 = (NetworkInfo[]) Arrays.copyOf(networks, networks.length + 1);
                                networks2[networks.length] = Info2;
                                param.setResult(networks2);
                            }
                        }
                    }
                }
            });
        } catch (Throwable t) {
            Log.e(TAG,"getAllNetworkInfo "+ t.getMessage());
        }

        if(lpparam.packageName.equals("android"))
        {
            Class<?> ams = XposedHelpers.findClass("com.android.server.am.ActivityManagerService", lpparam.classLoader);
            XposedBridge.hookAllMethods(ams, "systemReady",
                    new XC_MethodHook() {
                        @Override
                        protected final void afterHookedMethod(final MethodHookParam param) {
                            XposedBridge.log(TAG + "hookAllMethods bSystemIsReady = true " );
                            ｍbSystemIsReady = true;
                            mUpTimeSec =SystemClock.uptimeMillis()/1000;
                        }
                    }
            );


            Class<?> wifiManagerClazz = null;
            try {
                wifiManagerClazz = XposedHelpers.findClass("android.net.wifi.WifiManager", lpparam.classLoader);
            } catch (Throwable th) {
                XposedBridge.log(TAG + " WifiManager " + th.toString());
            }
            try {
                XposedHelpers.findAndHookMethod(wifiManagerClazz, "getConnectionInfo", new XC_MethodHook() {
                    public void afterHookedMethod(MethodHookParam param)  {
                        Log.d(TAG,"WifiManager getConnectionInfo");
                        if(context[0]==null)
                        {
                            XposedBridge.log(TAG + " getConnectionInfo context[0]==null");
                            return;
                        }
                        int nettype=ProviderUtils.getInstance(context[0]).getIntValue(ProviderUtils.KEY_NAME_NETTYPE);

                        //WIFI
                        if(nettype==NETWORK_TYPE_WIFI)
                        {
                            WifiInfo wifiInfo=(WifiInfo)param.getResult();
                            if(wifiInfo==null){
                                try {
                                    Constructor<?> riConstructor = WifiInfo.class.getConstructor();
                                    wifiInfo = (WifiInfo) riConstructor.newInstance();
                                    XposedHelpers.callMethod(wifiInfo,"setBSSID","f0:b4:29:54:5f:12");
                                    XposedHelpers.callMethod(wifiInfo,"setRssi",-36);
                                    XposedHelpers.callMethod(wifiInfo,"setLinkSpeed",866);
                                    XposedHelpers.callMethod(wifiInfo,"setFrequency",5785);
                                    XposedHelpers.callMethod(wifiInfo,"setNetworkId",0);
                                    XposedHelpers.callMethod(wifiInfo,"setFrequency",5785);
                                    XposedHelpers.setIntField(wifiInfo,"score",80);
                                    param.setResult(wifiInfo);
                                }catch (Exception e){
                                    Log.e(TAG,"New WifiInfo err:"+e.toString());
                                }

                            }

                      /*      Context context = (Context) XposedHelpers.getObjectField(param.thisObject, "mContext");
                            if (((SystemClock.uptimeMillis() / 1000 - mUpTimeSec) > 30) && (ｍbSystemIsReady)) {
                                XposedBridge.log(TAG + " sendConnectBroadCast");
                                mUpTimeSec = SystemClock.uptimeMillis() / 1000;
                                if (mLinkProperties == null)
                                    mLinkProperties=creatLinkProp(lpparam);

                                if (mNetworkInfo == null)
                                    mNetworkInfo=newNetWorkInfo(ConnectivityManager.TYPE_WIFI);

                                sendConnectBroadCast(context, true,ConnectivityManager.TYPE_WIFI);
                                sendBroadCastNetworkStateChange(context, "myWifi");
                            }*/
                        }
                    }
                });
            } catch (Throwable t) {
                XposedBridge.log(TAG + " WifiManager " + t.getMessage());
            }
        }
    }


    private void sendConnectBroadCast(Context context, boolean connected, int netType) {
        Intent intent = new Intent(CONNECTIVITY_ACTION);

        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, mNetworkInfo);
        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, netType);
        intent.putExtra("inetCondition", connected ? 100 : 0);
        try {
            Constructor<?> laConstructor = UserHandle.class.getConstructor( int.class);
            UserHandle userHandle = (UserHandle) laConstructor.newInstance(-1);
            context.sendStickyBroadcastAsUser(intent, userHandle);
        }catch (Exception e){
            Log.e(TAG,"sendConnectBroadCast "+e.toString());
        }
    }

    private void sendBroadCastNetworkStateChange(Context context,String bssid) {
        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        intent.addFlags(0x04000000);
        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
        intent.putExtra("linkProperties", (LinkProperties) mLinkProperties);
        if (bssid != null)
            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);

        try {
            Constructor<?> laConstructor = UserHandle.class.getConstructor( int.class);
            UserHandle userHandle = (UserHandle) laConstructor.newInstance(-1);
            context.sendStickyBroadcastAsUser(intent, userHandle);
        }catch (Exception e){
            Log.e(TAG,"sendNetworkStateChangeBroadcast "+e.toString());
        }
    }

    private  LinkProperties creatLinkProp(XC_LoadPackage.LoadPackageParam lpparam){
        LinkProperties linkProperties=null;
        try {
            Class<?> linkPropClazz =XposedHelpers.findClass("android.net.LinkProperties",lpparam.classLoader);

            getIpAndIf();

            linkProperties =(LinkProperties)linkPropClazz.newInstance();
            XposedHelpers.callMethod(linkProperties,"setInterfaceName",mIfName);
            XposedHelpers.callMethod(linkProperties,"setTcpBufferSizes",
                    "524288,1048576,2097152,262144,524288,1048576");

            Constructor<?> laConstructor = LinkAddress.class.getConstructor(InetAddress.class, int.class);
            LinkAddress linkAddress = (LinkAddress) laConstructor.newInstance(
                    InetAddress.getByName(mIpAddr),24);
            XposedHelpers.callMethod(linkProperties,"addLinkAddress",linkAddress);

            Constructor<?> riConstructor = RouteInfo.class.getConstructor(InetAddress.class);
            RouteInfo routeInfo = (RouteInfo) riConstructor.newInstance(InetAddress.getByName(mGateway));
            XposedHelpers.callMethod(linkProperties,"addRoute",routeInfo);

            XposedHelpers.callMethod(linkProperties,"addDnsServer",InetAddress.getByName("114.114.114.114"));
            XposedHelpers.callMethod(linkProperties,"addDnsServer",InetAddress.getByName("8.8.8.8"));

        }catch (Throwable t){
            XposedBridge.log(TAG+" creatLinkProp " + t.toString());
        }
        return linkProperties;
    }

/*    private  void creatNetworkInfo(int netType){
        try {
            Constructor<?> riConstructor = NetworkInfo.class.getConstructor(int.class,int.class,String.class,String.class);
            if(netType==ConnectivityManager.TYPE_MOBILE)
                mNetworkInfo = (NetworkInfo) riConstructor.newInstance(ConnectivityManager.TYPE_MOBILE,0,"4G","4g");
            else
                mNetworkInfo = (NetworkInfo) riConstructor.newInstance(ConnectivityManager.TYPE_WIFI,0,"WIFI","subWifi");
        }catch (Throwable t){
            XposedBridge.log(TAG+" creatNetworkInfo " + t.toString());
        }
    }*/

    private  void getIpAndIf() {
        try {
            Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface=(NetworkInterface) networkInterfaces.nextElement();
                String ifName=networkInterface.getName();
                if(TextUtils.isEmpty(ifName))
                    ifName=networkInterface.getDisplayName();

                Enumeration inetAddresses = networkInterface.getInetAddresses();

                while (inetAddresses.hasMoreElements()) {
                    InetAddress inetAddress = (InetAddress) inetAddresses.nextElement();

                    String hostAddress = inetAddress.getHostAddress();
                    if (!inetAddress.isLoopbackAddress()
                            && (inetAddress instanceof Inet4Address)
                               &&(ifName.equalsIgnoreCase("rndis0")
                               ||ifName.equalsIgnoreCase("wlan0"))) {
                        mIpAddr= hostAddress;
                        mIfName=ifName;
                        int lastDotIndex=mIpAddr.lastIndexOf('.');
                        mGateway=hostAddress.substring(0,lastDotIndex)+".1";
                        XposedBridge.log(TAG+" getIpAndIf mIpAddr:" + mIpAddr+" mIfName:"+mIfName+" mGateway:"+mGateway);
                        return ;
                    }
                }
            }
        } catch (Exception unused) {
            XposedBridge.log(TAG+" getIpAndIf " + unused.toString());
        }

    }

    private static NetworkInfo newNetWorkInfo(int networkType) {
        NetworkInfo result = null;

        try {
            Constructor<?> riConstructor = NetworkInfo.class.getConstructor(int.class,int.class,String.class,String.class);
            if(networkType==ConnectivityManager.TYPE_MOBILE)
                result = (NetworkInfo) riConstructor.newInstance(ConnectivityManager.TYPE_MOBILE,0,"4G","4g");
            else
                result = (NetworkInfo) riConstructor.newInstance(ConnectivityManager.TYPE_WIFI,0,"WIFI","subWifi");
            XposedHelpers.setBooleanField(result, "mIsAvailable", true);
            XposedHelpers.setObjectField(result, "mState", NetworkInfo.State.CONNECTED);
            XposedHelpers.setObjectField(result, "mDetailedState", NetworkInfo.DetailedState.CONNECTED);

        } catch (Exception e) {
            Log.e(TAG, "newNetWorkInfo "+e.toString());
        }

        return result;
    }



    private void fakeNetworkInfo(NetworkInfo networkInfo) {
        if (networkInfo != null) {
           try {
                if (!networkInfo.isAvailable()) {
                    XposedHelpers.setBooleanField(networkInfo, "mIsAvailable", true);
                }
                if (!networkInfo.isConnected()) {
                    XposedHelpers.setObjectField(networkInfo, "mState", NetworkInfo.State.CONNECTED);
                }
                if (networkInfo.getDetailedState() != NetworkInfo.DetailedState.CONNECTED) {
                    XposedHelpers.setObjectField(networkInfo, "mDetailedState", NetworkInfo.DetailedState.CONNECTED);
                }
            } catch (Exception e) {
                Log.e(TAG, "fakeNetworkInfo error : " + e.getMessage());
            }

        }
    }

}